﻿using System;
using System.Collections.Generic;
using System.Linq;
using MongoDB.Bson;

namespace CQRS.Events.DomainClass
{
    public class BaseEntity<TEvent> : IEventProvider<TEvent> where TEvent : Event
    {
        private readonly List<TEvent> _appliedEvents;
        private readonly Dictionary<Type, Action<TEvent>> _registeredEvents;

        public BaseEntity()
        {
            _registeredEvents = new Dictionary<Type, Action<TEvent>>();
            _appliedEvents = new List<TEvent>();
            EventProviderId = Guid.NewGuid();
        }

        #region IEventProvider<TEvent> Members

        public void UpdateVersion(int version)
        {
            Version = version;
        }

        public ObjectId Id { get; protected set; }

        public Guid EventProviderId
        {
            get;
            protected set;
        }

        public int Version { get; protected set; }

        void IEventProvider<TEvent>.LoadFromHistory(IList<TEvent> domainEvents)
        {
            if (domainEvents.Count() == 0)
                return;

            foreach (TEvent domainEvent in domainEvents)
            {
                ApplyEvent(domainEvent.GetType(), domainEvent);
            }
        }

        IEnumerable<TEvent> IEventProvider<TEvent>.GetChanges()
        {
            return _appliedEvents;
        }

        void IEventProvider<TEvent>.Clear()
        {
            _appliedEvents.Clear();
        }

        #endregion

        protected void RegisterEvent(Action<TEvent> eventHandler)
        {
            _registeredEvents.Add(typeof (TEvent), eventHandler);
        }

        protected void Apply(TEvent domainEvent)
        {
            domainEvent.AggregateId = Id;
            ApplyEvent(domainEvent.GetType(), domainEvent);
            _appliedEvents.Add(domainEvent);
        }

        private void ApplyEvent(Type eventType, TEvent domainEvent)
        {
            Action<TEvent> handler;

            if (!_registeredEvents.TryGetValue(eventType, out handler))
                throw new UnknownEventException(string.Format("The requested domain event" +
                                                              " '{0}' is not registered in '{1}'", eventType.FullName,
                                                              GetType().FullName));

            handler(domainEvent);
        }
    }
}